#include <jni.h>
#include <GLES3/gl3.h>
#include <gles_utility.h>
#include <string.h>

#ifdef __cplusplus
extern "C" {
#endif

#define VERTEX_POS_SIZE         3 // x, y and z
#define VERTEX_COLOR_SIZE       4 // r, g, b, and a
#define VERTEX_POS_INDX         0
#define VERTEX_COLOR_INDX       1
#define VERTEX_FLOAT_STRIDE     sizeof(GLfloat) * (VERTEX_POS_SIZE + VERTEX_COLOR_SIZE)
#define VERTEX_USHORT_STRIDE    sizeof(GLushort) * (VERTEX_POS_SIZE + VERTEX_COLOR_SIZE)
#define VBO_COUNT               2
#define VERTEX_POS_OFFSET       0
#define VERTEX_COLOR_OFFSET     VERTEX_POS_SIZE

typedef struct {
    GLuint programObject;
    // VertexBufferObject Ids
    GLuint vboIds[VBO_COUNT];
    // x-offset uniform location
    GLint offsetLoc;
} SampleBean;

static SampleBean samBean;

void DrawPrimitiveWithoutVBO(GLfloat *vertices, GLuint numIndices, GLushort *indices) {
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glEnableVertexAttribArray(VERTEX_POS_INDX);
    glEnableVertexAttribArray(VERTEX_COLOR_INDX);
    glVertexAttribPointer(VERTEX_POS_INDX, VERTEX_POS_SIZE, GL_FLOAT, GL_FALSE, VERTEX_FLOAT_STRIDE,
                          vertices);
    glVertexAttribPointer(VERTEX_COLOR_INDX, VERTEX_COLOR_SIZE, GL_FLOAT, GL_FALSE,
                          VERTEX_FLOAT_STRIDE, vertices + VERTEX_COLOR_OFFSET);
    glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT, indices);
    glDisableVertexAttribArray(VERTEX_POS_INDX);
    glDisableVertexAttribArray(VERTEX_COLOR_INDX);
}

void
DrawPrimitiveWithVBO(GLuint numVertices, GLfloat *vertices, GLuint numIndices, GLushort *indices) {
    glGenBuffers(VBO_COUNT, samBean.vboIds);
    glBindBuffer(GL_ARRAY_BUFFER, samBean.vboIds[0]);
    glBufferData(GL_ARRAY_BUFFER, VERTEX_FLOAT_STRIDE * numVertices, vertices, GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, samBean.vboIds[1]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, VERTEX_USHORT_STRIDE, indices, GL_STATIC_DRAW);

    glEnableVertexAttribArray(VERTEX_POS_INDX);
    glEnableVertexAttribArray(VERTEX_COLOR_INDX);

    glVertexAttribPointer(VERTEX_POS_INDX, VERTEX_POS_SIZE, GL_FLOAT, GL_FALSE, VERTEX_FLOAT_STRIDE,
                          nullptr);
    /**
     * glVertexAttribPointer(index, size, type, normalized, stride, pointer);
     * @param pointer 如果使用客户端顶点数组，则是保存顶点属性数据的缓冲区的指针；而如果使用顶点缓冲区对象，则表示该缓冲区内的偏移量
     * <p>
     * 显然这里用的是顶点缓冲区对象，第一个属性索引为{@code a_position}，即顶点坐标属性，
     * 每个顶点坐标由三个{@code GLfloat}类型的数字组成（x,y,z），所以，第二个顶点属性相对于第一个顶点属性的偏移量为
     * {@code sizeof(GLfloat)*VERTEX_POS_SIZE}
     */
    glVertexAttribPointer(VERTEX_COLOR_INDX, VERTEX_COLOR_SIZE, GL_FLOAT, GL_FALSE,
                          VERTEX_FLOAT_STRIDE,
                          (const void *) (sizeof(GLfloat) * VERTEX_POS_SIZE));
    glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT, nullptr);
    glDisableVertexAttribArray(VERTEX_POS_INDX);
    glDisableVertexAttribArray(VERTEX_COLOR_INDX);

}

JNIEXPORT void JNICALL
Java_com_andova_egl_VBO_00024Companion_surfCreated(JNIEnv *, jobject) {
    memset(&samBean, 0, sizeof(SampleBean));
    const char vShaderStr[] =
            "#version 300 es                            \n"
            "layout(location = 0) in vec4 a_position;   \n"
            "layout(location = 1) in vec4 a_color;      \n"
            "uniform float u_offset;                    \n"
            "flat out vec4 v_color;                          \n"
            "void main()                                \n"
            "{                                          \n"
            "   v_color = a_color;                      \n"
            "   gl_Position = a_position;               \n"
            "   gl_Position.x += u_offset;              \n"
            "}";
    const char fShaderStr[] =
            "#version 300 es           \n"
            "precision mediump float;  \n"
            "flat in vec4 v_color;          \n"
            "out vec4 o_fragColor;     \n"
            "void main()               \n"
            "{                         \n"
            "   o_fragColor = v_color; \n"
            "}";
    samBean.programObject = glesInitialize(vShaderStr, fShaderStr);
    samBean.offsetLoc = glGetUniformLocation(samBean.programObject, "u_offset");
    glClearColor(1.0F, 1.0F, 1.0F, 0.0F);
    glesLogIntegerv(samBean.programObject);
}

JNIEXPORT void JNICALL
Java_com_andova_egl_VBO_00024Companion_surfChanged(JNIEnv *, jobject, jint w, jint h) {
    glViewport(0, 0, w, h);
}

JNIEXPORT void JNICALL
Java_com_andova_egl_VBO_00024Companion_drawFrame(JNIEnv *, jobject) {
    // 3 vertices, with (x,y,z) ,(r, g, b, a) per-vertex
    GLfloat vertices[3 * (VERTEX_POS_SIZE + VERTEX_COLOR_SIZE)] =
            {
                    0.0F, 0.5F, 0.0F,       // v0
                    1.0F, 0.0F, 0.0F, 1.0F, // c0
                    -0.5F, -0.5F, 0.0F,     // v1
                    0.0F, 1.0F, 0.0F, 1.0F, // c1
                    0.5F, -0.5F, 0.0F,      // v2
                    0.0F, 0.0F, 1.0F, 1.0F, // c2
            };
    // Index buffer data
    GLushort indices[3] = {0, 1, 2};
    glClear(GL_COLOR_BUFFER_BIT);
    glClearColor(0, 0.4, 0, 1);
    glUseProgram(samBean.programObject);
    glUniform1f(samBean.offsetLoc, -0.5F);
    DrawPrimitiveWithoutVBO(vertices, 3, indices);
    glUniform1f(samBean.offsetLoc, 0.5F);
    DrawPrimitiveWithVBO(3, vertices, 3, indices);
}

#ifdef __cplusplus
}
#endif

